const assert = require("assert");
const Asserts = require("../utils/asserts");
const Helpers = require("./helpers");

Feature("Image transformer");

const IMAGE = "https://data.heartex.net/open-images/train_0/mini/0030019819f25b28.jpg";

const annotationEmpty = {
  id: "1000",
  result: [],
};

const getParamsWithShape = (shape, params = "") => ({
  config: `
  <View>
    <Image name="img" value="$image" />
    <${shape} ${params} name="tag" toName="img" />
  </View>`,
  data: { image: IMAGE },
  annotations: [annotationEmpty],
});

const getParamsWithLabels = (shape) => ({
  config: `
  <View>
    <Image name="img" value="$image" />
    <${shape}Labels name="tag" toName="img">
      <Label value="${shape}" background="orange"/>
    </${shape}Labels>
  </View>`,
  data: { image: IMAGE },
  annotations: [annotationEmpty],
});

const shapes = {
  Rectangle: {
    drawAction: "drawByDrag",
    hasTransformer: true,
    hasRotator: true,
    hasMoveToolTransformer: true,
    hasMultiSelectionTransformer: true,
    hasMultiSelectionRotator: true,
    hotKey: "r",
    byBBox(x, y, width, height) {
      return {
        params: [x, y, width, height],
        result: { width, height, rotation: 0, x, y },
      };
    },
  },
  Ellipse: {
    drawAction: "drawByDrag",
    hasTransformer: true,
    hasRotator: true,
    hasMoveToolTransformer: true,
    hasMultiSelectionTransformer: true,
    hasMultiSelectionRotator: true,
    hotKey: "o",
    byBBox(x, y, width, height) {
      return {
        params: [x + width / 2, y + height / 2, width / 2, height / 2],
        result: {
          radiusX: width / 2,
          radiusY: height / 2,
          rotation: 0,
          x: x + width / 2,
          y: y + height / 2,
        },
      };
    },
  },
  Polygon: {
    drawAction: "drawByClickingPoints",
    hasTransformer: false,
    hasRotator: false,
    hasMoveToolTransformer: true,
    hasMultiSelectionTransformer: true,
    hasMultiSelectionRotator: false,
    hotKey: "p",
    byBBox(x, y, width, height) {
      const points = [];

      points.push([x, y]);
      points.push([x + width, y]);
      points.push([x + width / 2, y + height / 2]);
      points.push([x + width, y + height]);
      points.push([x, y + height]);
      return {
        params: [[...points, points[0]]],
        result: {
          points,
          closed: true,
        },
      };
    },
  },
  KeyPoint: {
    drawAction: "clickAt",
    hasTransformer: false,
    hasRotator: false,
    hasMoveToolTransformer: false,
    hasMultiSelectionTransformer: true,
    hasMultiSelectionRotator: false,
    hotKey: "k",
    params: 'strokeWidth="2"',
    byBBox(x, y, width, height) {
      return {
        params: [x + width / 2, y + height / 2],
        result: {
          x: x + width / 2,
          y: y + height / 2,
          width: 2,
        },
      };
    },
  },
};

function drawShapeByBbox(Shape, x, y, width, height, where) {
  where[Shape.drawAction](...Shape.byBBox(x, y, width, height).params);
}

const shapesTable = new DataTable(["shapeName"]);

for (const shapeName of Object.keys(shapes)) {
  shapesTable.add([shapeName]);
}

Data(shapesTable)
  .Scenario(
    "Check transformer existing for different shapes, their amount and modes.",
    async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
      const { shapeName } = current;
      const Shape = shapes[shapeName];

      I.amOnPage("/");
      const bbox1 = {
        x: 100,
        y: 100,
        width: 200,
        height: 200,
      };
      const bbox2 = {
        x: 400,
        y: 100,
        width: 200,
        height: 200,
      };
      const getCenter = (bbox) => [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
      let isTransformerExist;
      const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

      LabelStudio.init(getParamsWithLabels(shapeName));
      AtDetailsPanel.collapsePanel();

      LabelStudio.waitForObjectsReady();
      AtOutliner.seeRegions(0);
      await AtImageView.lookForStage();

      // Draw two regions
      I.pressKey("1");
      drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
      AtOutliner.seeRegions(1);
      I.pressKey("1");
      drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
      AtOutliner.seeRegions(2);

      // Check that it wasn't a cause to show a transformer
      isTransformerExist = await AtImageView.isTransformerExist();
      assert.strictEqual(isTransformerExist, false);

      // Select the first region
      AtImageView.clickAt(...getCenter(bbox1));
      AtOutliner.seeSelectedRegion();

      // Match if transformer exist with expectations in single selected mode
      isTransformerExist = await AtImageView.isTransformerExist();
      assert.strictEqual(isTransformerExist, Shape.hasTransformer);

      // Match if rotator at transformer exist with expectations in single selected mode
      isTransformerExist = await AtImageView.isRotaterExist();
      assert.strictEqual(isTransformerExist, Shape.hasRotator);

      // Switch to move tool
      I.pressKey("v");

      // Match if rotator at transformer exist with expectations in single selected mode with move tool chosen
      isTransformerExist = await AtImageView.isTransformerExist();
      assert.strictEqual(isTransformerExist, Shape.hasMoveToolTransformer);

      // Deselect the previous selected region
      I.pressKey(["u"]);

      // Select 2 regions
      AtImageView.drawThroughPoints(
        [
          [bbox1.x - 5, bbox1.y - 5],
          [bbox2.x + bbox2.width + 5, bbox2.y + bbox2.height + 5],
        ],
        "steps",
        10,
      );

      // Match if transformer exist with expectations in multiple selected mode
      isTransformerExist = await AtImageView.isTransformerExist();
      assert.strictEqual(isTransformerExist, Shape.hasMultiSelectionTransformer);

      // Match if rotator exist with expectations in multiple selected mode
      isTransformerExist = await AtImageView.isRotaterExist();
      assert.strictEqual(isTransformerExist, Shape.hasMultiSelectionRotator);
    },
  )
  .retry(3);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer)).Scenario(
  "Resizing a single region",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();
    const canvasSize = await AtImageView.getCanvasSize();
    const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);

    // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150}
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, 50, 50, 100, 100, AtImageView);
    AtOutliner.seeRegions(1);
    AtOutliner.dontSeeIncompleteRegion();

    // Select the shape
    AtImageView.clickAt(100, 100);
    AtOutliner.seeSelectedRegion();

    // Switch to move tool to force appearance of transformer
    I.pressKey("v");
    const isTransformerExist = await AtImageView.isTransformerExist();

    assert.strictEqual(isTransformerExist, true);

    // Transform the shape
    // Move the top anchor up for 50px (limited by image border) => {x1:50,y1:0,x2:150,y2:150}
    AtImageView.drawByDrag(100, 50, 0, -100);
    // Move the left anchor left for 50px (limited by image border) => {x1:0,y1:0,x2:150,y2:150}
    AtImageView.drawByDrag(50, 75, -300, -100);
    // Move the right anchor left for 50px => {x1:0,y1:0,x2:100,y2:150}
    AtImageView.drawByDrag(150, 75, -50, 0);
    // Move the bottom anchor down for 100px => {x1:0,y1:0,x2:100,y2:250}
    AtImageView.drawByDrag(50, 150, 10, 100);
    // Move the right-bottom anchor right for 200px and down for 50px => {x1:0,y1:0,x2:300,y2:300}
    AtImageView.drawByDrag(100, 250, 200, 50);
    // Check resulting sizes
    const rectangleResult = await LabelStudio.serialize();
    const exceptedResult = Shape.byBBox(0, 0, 300, 300).result;

    Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult));
  },
);

// Currently flipping is handled correctly only for rectangles.
Data(shapesTable.filter(({ shapeName }) => shapeName === "Rectangle")).Scenario(
  "Flip region during resizing",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();
    const canvasSize = await AtImageView.getCanvasSize();
    const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);
    let rectangleResult;

    // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150}
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, 50, 50, 100, 100, AtImageView);
    AtOutliner.seeRegions(1);
    AtOutliner.dontSeeIncompleteRegion();

    // Select the shape
    AtImageView.clickAt(100, 100);
    AtOutliner.seeSelectedRegion();

    // Switch to move tool to force appearance of transformer
    I.pressKey("v");
    const isTransformerExist = await AtImageView.isTransformerExist();

    assert.strictEqual(isTransformerExist, true);

    // Flip the shape horizontally
    // Move the left anchor to the right further than region width, effectively flipping it and reducing width to 50px
    AtImageView.drawByDrag(50, 100, 150, 0);
    // Check resulting sizes
    rectangleResult = await LabelStudio.serialize();
    const exceptedResult = Shape.byBBox(150, 50, 50, 100).result;
    Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult));

    // new center of the region
    const center = [150 + 25, 50 + 50];

    // Rotate the shape by 45 degrees, rotation handle is 50px above the top anchor
    // we move the rotation handle to the right to rotate the shape by 45 degrees
    AtImageView.drawByDrag(center[0], 0, center[1], 0);

    rectangleResult = await LabelStudio.serialize();
    Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 45);
    // Flip the shape horizontally with non-zero rotation
    const shift = (50 * 2) / Math.SQRT2;
    AtImageView.drawByDrag(center[0] - 25 / Math.SQRT2, center[1] - 25 / Math.SQRT2, shift, shift);

    const secondFlipResult = {
      ...convertToImageSize(
        Shape.byBBox(
          center[0] + 25 / Math.SQRT2 + 50 / Math.SQRT2,
          center[1] + 25 / Math.SQRT2 - 50 / Math.SQRT2,
          50,
          100,
        ).result,
      ),
      rotation: 45,
    };

    rectangleResult = await LabelStudio.serialize();
    // flipping is not very precise, so we have to increase the tolerance
    Asserts.deepEqualWithTolerance(rectangleResult[0].value, secondFlipResult, 0);
  },
);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMoveToolTransformer)).Scenario(
  "Resizing a single region with zoom",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, Regions, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    LabelStudio.setFeatureFlags({
      fflag_fix_front_dev_3377_image_regions_shift_on_resize_280922_short: true,
      fflag_fix_front_dev_3793_relative_coords_short: true,
    });

    I.amOnPage("/");

    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    AtDetailsPanel.seeExpandButton();
    await AtImageView.lookForStage();

    // Draw a region in bbox {x1:50,y1:50,x2:150,y2:150}
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, 50, 50, 300, 300, AtImageView);
    AtOutliner.seeRegions(1);
    AtOutliner.dontSeeIncompleteRegion();

    // Select the shape
    AtImageView.clickAt(100, 100);
    AtOutliner.seeSelectedRegion();

    // Switch to move tool to force appearance of transformer
    I.pressKey("v");
    const isTransformerExist = await AtImageView.isTransformerExist();

    assert.strictEqual(isTransformerExist, true);

    // it won't be real zoom scale, so we have to compensate it,
    // and in the current specific situation it should be done my maxScale
    const { maxScale } = await AtImageView.getZoomProps();
    AtImageView.setZoom(3 * maxScale, 0, 0);

    await AtImageView.lookForStage();
    const prevRegionBBox = await Regions.getBBoxByRegionIdx(0);

    // Transform the shape
    AtImageView.drawByDrag(150, 150, -150, -150);

    AtImageView.drawByDrag(0, 0, -300, -100);

    AtImageView.drawByDrag(0, 0, 150, 150);

    // Check resulting sizes
    const regionBBox = await Regions.getBBoxByRegionIdx(0);

    Asserts.deepEqualWithTolerance(regionBBox, prevRegionBBox, 2);
  },
);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)).Scenario(
  "Simple rotating",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();
    const canvasSize = await AtImageView.getCanvasSize();

    // Draw a region in bbox {x1:40%,y1:40%,x2:60%,y2:60%}
    const rectangle = {
      x: canvasSize.width * 0.4,
      y: canvasSize.height * 0.4,
      width: canvasSize.width * 0.2,
      height: canvasSize.height * 0.2,
    };
    const rectangleCenter = {
      x: rectangle.x + rectangle.width / 2,
      y: rectangle.y + rectangle.height / 2,
    };

    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
    AtOutliner.seeRegions(1);

    // Select the shape and check that transformer appears
    AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
    AtOutliner.seeSelectedRegion();

    // Switch to move tool to force appearance of transformer
    I.pressKey("v");
    const isTransformerExist = await AtImageView.isTransformerExist();

    assert.strictEqual(isTransformerExist, true);

    // The rotator anchor must be above top anchor by 50 pixels
    const rotatorPosition = {
      x: rectangleCenter.x,
      y: rectangle.y - 50,
    };

    // Rotate for 45 degrees clockwise
    AtImageView.drawThroughPoints(
      [
        [rotatorPosition.x, rotatorPosition.y],
        [rectangleCenter.x + 500, rectangleCenter.y - 500],
      ],
      "steps",
      5,
    );

    // Check resulting rotation
    const rectangleResult = await LabelStudio.serialize();

    Asserts.deepEqualWithTolerance(Math.round(rectangleResult[0].value.rotation), 45);
  },
);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)).Scenario(
  "Rotating of unrotatable region",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();
    const canvasSize = await AtImageView.getCanvasSize();

    // Draw a region which we cannot rotate 'cause of position near the image's border {x1:0,y1:20%,x2:20%,y2:50%}
    const rectangle = {
      x: 0,
      y: canvasSize.height * 0.2,
      width: canvasSize.width * 0.2,
      height: canvasSize.height * 0.3,
    };
    const rectangleCenter = {
      x: rectangle.x + rectangle.width / 2,
      y: rectangle.y + rectangle.height / 2,
    };

    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
    AtOutliner.seeRegions(1);

    // Select the shape and check that transformer appears
    AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
    AtOutliner.seeSelectedRegion();

    // Switch to move tool to force appearance of transformer
    I.pressKey("v");
    const isTransformerExist = await AtImageView.isTransformerExist();

    assert.strictEqual(isTransformerExist, true);

    // The rotator anchor must be above top anchor by 50 pixels
    const rotatorPosition = {
      x: rectangleCenter.x,
      y: rectangle.y - 50,
    };

    // Rotate for 45 degrees clockwise
    AtImageView.drawByDrag(rotatorPosition.x, rotatorPosition.y, rectangleCenter.y - rotatorPosition.y + 100, -100);

    // Check the region hasn't been rotated
    const rectangleResult = await LabelStudio.serialize();

    Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 0);
  },
);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)).Scenario(
  "Broke the limits with rotation",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();
    const canvasSize = await AtImageView.getCanvasSize();

    {
      // Draw a region which have limitation at rotating by bbox {x1:5,y1:100,x2:305,y2:350}
      const rectangle = {
        x: 5,
        y: 100,
        width: 300,
        height: 300,
      };
      const rectangleCenter = {
        x: rectangle.x + rectangle.width / 2,
        y: rectangle.y + rectangle.height / 2,
      };

      I.pressKey(Shape.hotKey);
      drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
      AtOutliner.seeRegions(1);

      // Select the shape and check that transformer appears
      AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
      AtOutliner.seeSelectedRegion();

      // Switch to move tool to force appearance of transformer
      I.pressKey("v");
      const isTransformerExist = await AtImageView.isTransformerExist();

      assert.strictEqual(isTransformerExist, true);

      // The rotator anchor must be above top anchor by 50 pixels
      const rotatorPosition = {
        x: rectangleCenter.x,
        y: rectangle.y - 50,
      };

      // Rotate for 45 degrees clockwise
      AtImageView.drawThroughPoints(
        [
          [rotatorPosition.x, rotatorPosition.y],
          [rectangleCenter.x + 500, rectangleCenter.y - 500],
        ],
        "steps",
        200,
      );

      // Check that we cannot rotate it like this
      let rectangleResult = await LabelStudio.serialize();

      assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 0, "Region must be rotated");
      assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 45, "Angle must not be 45 degrees");

      // Undo changes
      I.pressKey(["CommandOrControl", "z"]);

      // Rotate for 90 degrees clockwise instead
      AtImageView.drawThroughPoints(
        [
          [rotatorPosition.x, rotatorPosition.y],
          [rectangle.x + rectangle.width + 100, rectangleCenter.y],
          [rectangle.x + rectangle.width + 200, rectangleCenter.y],
        ],
        "steps",
        200,
      );

      // Check the resulted rotation
      rectangleResult = await LabelStudio.serialize();

      Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 90, "Angle must be 90 degrees");
      // remove region
      I.pressKey("Backspace");
    }

    I.say("Check that it works same way with right border");

    {
      // Draw a region which have limitation at rotating by bbox {x1:100% - 305,y1:100,x2:100% - 5,y2:350}
      const rectangle = {
        x: canvasSize.width - 305,
        y: 100,
        width: 300,
        height: 300,
      };
      const rectangleCenter = {
        x: rectangle.x + rectangle.width / 2,
        y: rectangle.y + rectangle.height / 2,
      };

      I.pressKey(Shape.hotKey);
      drawShapeByBbox(Shape, rectangle.x, rectangle.y, rectangle.width, rectangle.height, AtImageView);
      AtOutliner.seeRegions(1);

      // Select the shape and check that transformer appears
      AtImageView.clickAt(rectangleCenter.x, rectangleCenter.y);
      AtOutliner.seeSelectedRegion();

      // Switch to move tool to force appearance of transformer
      I.pressKey("v");
      const isTransformerExist = await AtImageView.isTransformerExist();

      assert.strictEqual(isTransformerExist, true);

      // The rotator anchor must be above top anchor by 50 pixels
      const rotatorPosition = {
        x: rectangleCenter.x,
        y: rectangle.y - 50,
      };

      // Rotate for 45 degrees clockwise
      AtImageView.drawThroughPoints(
        [
          [rotatorPosition.x, rotatorPosition.y],
          [rectangleCenter.x + 500, rectangleCenter.y - 500],
        ],
        "steps",
        200,
      );

      // Check the resulted rotation
      let rectangleResult = await LabelStudio.serialize();

      assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 0);
      assert.notStrictEqual(Math.round(rectangleResult[0].value.rotation), 45);

      // Undo changes
      I.pressKey(["CommandOrControl", "z"]);

      // Rotate for 90 degrees clockwise instead
      AtImageView.drawThroughPoints(
        [
          [rotatorPosition.x, rotatorPosition.y],
          [rectangle.x + rectangle.width + 100, rectangleCenter.y],
          [rectangle.x + rectangle.width + 200, rectangleCenter.y],
        ],
        "steps",
        200,
      );

      // Check that we cannot rotate it like this
      rectangleResult = await LabelStudio.serialize();

      Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 90);
    }
  },
);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)).Scenario(
  "Check the initial rotation of transformer for the single region",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();

    const bbox = {
      x: 100,
      y: 100,
      width: 100,
      height: 100,
    };
    const bboxCenter = {
      x: bbox.x + bbox.width / 2,
      y: bbox.y + bbox.height / 2,
    };

    // Draw a region
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView);
    AtOutliner.seeRegions(1);

    // Select it
    AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
    AtOutliner.seeSelectedRegion();

    // Switch to move tool to force appearance of transformer
    I.pressKey("v");
    const isTransformerExist = await AtImageView.isTransformerExist();

    assert.strictEqual(isTransformerExist, true);

    // The rotator anchor must be above top anchor by 50 pixels
    let rotatorPosition = {
      x: bboxCenter.x,
      y: bbox.y - 50,
    };

    // Rotate for 90 degrees clockwise
    AtImageView.drawThroughPoints(
      [
        [rotatorPosition.x, rotatorPosition.y],
        [bbox.x + bbox.width + 100, bboxCenter.y],
        [bbox.x + bbox.width + 200, bboxCenter.y],
      ],
      "steps",
      10,
    );

    // Unselect current region
    I.pressKey("u");
    AtOutliner.dontSeeSelectedRegion();

    // Select it again
    AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
    AtOutliner.seeSelectedRegion();

    // Wait for transformer to be properly initialized after re-selection
    I.waitTicks(3);

    // The trick is that we turn it further, based on the assumption that transformer appears in rotated state on region selection
    // So let's try to rotate it
    // The rotator anchor must be to the right of the right anchor by 50 pixels

    rotatorPosition = {
      x: bbox.x + bbox.width + 50,
      y: bboxCenter.y,
    };

    // Rotate for 90 degrees clockwise once again
    AtImageView.drawThroughPoints(
      [
        [rotatorPosition.x, rotatorPosition.y],
        [bboxCenter.x, bbox.y + bbox.height + 100],
        [bboxCenter.x, bbox.y + bbox.height + 200],
      ],
      "steps",
      10,
    );

    // Check that region has been rotated for 180 degrees
    const rectangleResult = await LabelStudio.serialize();

    Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 180);
  },
);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)).Scenario(
  "Check the initial rotation of transformer for the couple of regions",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();

    const bbox1 = {
      x: 100,
      y: 100,
      width: 40,
      height: 40,
    };

    const bbox2 = {
      x: 160,
      y: 160,
      width: 40,
      height: 40,
    };

    const transformerBbox = {
      x: bbox1.x,
      y: bbox1.y,
      width: bbox2.x + bbox2.width - bbox1.x,
      height: bbox2.y + bbox2.height - bbox1.y,
    };
    const transformerBboxCenter = {
      x: transformerBbox.x + transformerBbox.width / 2,
      y: transformerBbox.y + transformerBbox.height / 2,
    };

    // Draw the first region
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
    AtOutliner.seeRegions(1);

    // Draw the second region
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
    AtOutliner.seeRegions(2);

    // Switch to move tool and select them
    I.pressKey("v");
    AtImageView.drawThroughPoints([
      [transformerBbox.x - 20, transformerBbox.y - 20],
      [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
    ]);
    AtOutliner.seeSelectedRegion();

    // The rotator anchor must be above top anchor by 50 pixels
    const rotatorPosition = {
      x: transformerBboxCenter.x,
      y: transformerBbox.y - 50,
    };

    // Rotate for 180 degrees clockwise
    AtImageView.drawThroughPoints(
      [
        [rotatorPosition.x, rotatorPosition.y],
        [transformerBboxCenter.x + 100, transformerBboxCenter.y + 100],
        [transformerBboxCenter.x, transformerBboxCenter.y + 100],
        [transformerBboxCenter.x, transformerBboxCenter.y + 200],
      ],
      "steps",
      10,
    );

    // Unselect current regions
    I.pressKey("u");
    AtOutliner.dontSeeSelectedRegion();

    // Select them again
    AtImageView.drawThroughPoints([
      [transformerBbox.x - 20, transformerBbox.y - 20],
      [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
    ]);
    AtOutliner.seeSelectedRegion();

    // Wait for transformer to be properly initialized after re-selection
    I.waitTicks(3);

    // So we have couple of rotated regions, let's check if rotates still appears above the top anchor

    // Rotate for 90 degrees clockwise
    AtImageView.drawThroughPoints(
      [
        [rotatorPosition.x, rotatorPosition.y],
        [transformerBboxCenter.x + 100, transformerBboxCenter.y],
        [transformerBboxCenter.x + 200, transformerBboxCenter.y],
      ],
      "steps",
      10,
    );

    // Check that region has been rotated for (180 + 90) degrees
    const rectangleResult = await LabelStudio.serialize();

    Asserts.deepEqualWithTolerance(rectangleResult[0].value.rotation, 180 + 90);
  },
);

// KeyPoints are transformed unpredictable so for now just skip them
Data(
  shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionTransformer && shapeName !== "KeyPoint"),
).Scenario(
  "Transforming of multiple regions",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();
    const canvasSize = await AtImageView.getCanvasSize();
    const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);

    const bbox1 = {
      x: 100,
      y: 100,
      width: 50,
      height: 50,
    };

    const bbox2 = {
      x: 150,
      y: 150,
      width: 50,
      height: 50,
    };

    const transformerBbox = {
      x: bbox1.x,
      y: bbox1.y,
      width: bbox2.x + bbox2.width - bbox1.x,
      height: bbox2.y + bbox2.height - bbox1.y,
    };
    const transformerBboxCenter = {
      get x() {
        return transformerBbox.x + transformerBbox.width / 2;
      },
      get y() {
        return transformerBbox.y + transformerBbox.height / 2;
      },
    };

    // Draw the first region
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
    AtOutliner.seeRegions(1);

    // Draw the second region
    I.pressKey(Shape.hotKey);
    I.pressKeyDown("CommandOrControl");
    drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
    I.pressKeyUp("CommandOrControl");
    AtOutliner.seeRegions(2);

    // Switch to move tool and select them
    I.pressKey("v");
    AtImageView.drawThroughPoints([
      [transformerBbox.x - 20, transformerBbox.y - 20],
      [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
    ]);
    AtOutliner.seeSelectedRegion();
    // Scale the shapes vertically
    AtImageView.drawByDrag(transformerBboxCenter.x, transformerBbox.y + transformerBbox.height, 0, 50);
    transformerBbox.height += 50;
    AtOutliner.seeSelectedRegion();
    // Scale the shapes horizontally
    AtImageView.drawByDrag(transformerBbox.x + transformerBbox.width, transformerBboxCenter.y, 50, 0);
    transformerBbox.width += 50;
    AtOutliner.seeSelectedRegion();
    // Scale the shapes in both directions
    AtImageView.drawByDrag(
      transformerBbox.x + transformerBbox.width,
      transformerBbox.y + transformerBbox.height,
      50,
      50,
    );
    transformerBbox.height += 50;
    transformerBbox.width += 50;
    AtOutliner.seeSelectedRegion();

    // Check resulting sizes
    const rectangleResult = await LabelStudio.serialize();
    const exceptedResult1 = Shape.byBBox(bbox1.x, bbox1.y, bbox1.width + 50, bbox1.height + 50).result;
    const exceptedResult2 = Shape.byBBox(bbox2.x + 50, bbox2.y + 50, bbox2.width + 50, bbox2.height + 50).result;

    Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult1));
    Asserts.deepEqualWithTolerance(rectangleResult[1].value, convertToImageSize(exceptedResult2));
  },
);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionTransformer)).Scenario(
  "Move regions by drag",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();
    const canvasSize = await AtImageView.getCanvasSize();
    const convertToImageSize = Helpers.getSizeConvertor(canvasSize.width, canvasSize.height);

    const bbox1 = {
      x: 100,
      y: 100,
      width: 20,
      height: 20,
    };
    const bbox1Center = {
      x: bbox1.x + bbox1.width / 2,
      y: bbox1.y + bbox1.height / 2,
    };

    const bbox2 = {
      x: 140,
      y: 140,
      width: 20,
      height: 20,
    };
    const bbox2Center = {
      x: bbox2.x + bbox2.width / 2,
      y: bbox2.y + bbox2.height / 2,
    };

    const transformerBbox = {
      x: bbox1.x,
      y: bbox1.y,
      width: bbox2.x + bbox2.width - bbox1.x,
      height: bbox2.y + bbox2.height - bbox1.y,
    };
    const transformerBboxCenter = {
      x: transformerBbox.x + transformerBbox.width / 2,
      y: transformerBbox.y + transformerBbox.height / 2,
    };

    // Draw the first region
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
    AtOutliner.seeRegions(1);

    // Draw the second region
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
    AtOutliner.seeRegions(2);

    if (shapeName === "KeyPoint") {
      // Draw more points to get more space in transformer
      I.pressKey(Shape.hotKey);
      drawShapeByBbox(Shape, bbox1.x, bbox1.y, 0, 0, AtImageView);
      AtOutliner.seeRegions(3);

      I.pressKey(Shape.hotKey);
      drawShapeByBbox(Shape, bbox2.x + bbox2.width, bbox2.y + bbox2.height, 0, 0, AtImageView);
      AtOutliner.seeRegions(4);
    }

    // Switch to move tool and select them
    I.pressKey("v");
    AtImageView.drawThroughPoints([
      [transformerBbox.x - 20, transformerBbox.y - 20],
      [transformerBbox.x + transformerBbox.width + 20, transformerBbox.y + transformerBbox.height + 20],
    ]);
    AtOutliner.seeSelectedRegion();

    const dragShapes = (startPoint, shift, rememberShift = true) => {
      AtImageView.drawThroughPoints(
        [
          [startPoint.x, startPoint.y],
          [startPoint.x + shift.x, startPoint.y + shift.y],
          [startPoint.x + shift.x, startPoint.y + shift.y],
        ],
        "steps",
        10,
      );
      AtOutliner.seeSelectedRegion();

      if (rememberShift) {
        bbox1Center.x += shift.x;
        bbox1Center.y += shift.y;
        bbox2Center.x += shift.x;
        bbox2Center.y += shift.y;
        transformerBboxCenter.x += shift.x;
        transformerBboxCenter.y += shift.y;
      }
    };

    // Drag shapes by holding onto the first region
    dragShapes(bbox1Center, { x: 100, y: 0 });
    // Drag shapes by holding onto the second region
    dragShapes(bbox2Center, { x: 0, y: 100 });
    // Drag shapes by holding onto the transformer background
    dragShapes(transformerBboxCenter, { x: 150, y: 150 }, false);
    // Move back throught history to check that transformer's background moving with it
    I.pressKey(["CommandOrControl", "z"]);
    // Drag shapes by holding onto the transformer background again
    dragShapes(transformerBboxCenter, { x: 100, y: 100 }, false);

    // Check that dragging was successful
    const rectangleResult = await LabelStudio.serialize();
    const exceptedResult1 = Shape.byBBox(bbox1.x + 200, bbox1.y + 200, bbox1.width, bbox1.height).result;
    const exceptedResult2 = Shape.byBBox(bbox2.x + 200, bbox2.y + 200, bbox2.width, bbox2.height).result;

    Asserts.deepEqualWithTolerance(rectangleResult[0].value, convertToImageSize(exceptedResult1));
    Asserts.deepEqualWithTolerance(rectangleResult[1].value, convertToImageSize(exceptedResult2));
  },
);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)).Scenario(
  "Limitation of dragging a single rotated region",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();
    const canvasSize = await AtImageView.getCanvasSize();

    const bbox = {
      x: canvasSize.width / 2 - 50,
      y: canvasSize.height / 2 - 50,
      width: 100,
      height: 100,
    };
    const bboxCenter = {
      x: bbox.x + bbox.width / 2,
      y: bbox.y + bbox.height / 2,
    };

    // Draw a region
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView);
    AtOutliner.seeRegions(1);

    // Select it
    AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
    AtOutliner.seeSelectedRegion();

    // Switch to move tool to force appearance of transformer
    I.pressKey("v");
    const isTransformerExist = await AtImageView.isTransformerExist();

    assert.strictEqual(isTransformerExist, true);

    // The rotator anchor must be above top anchor by 50 pixels
    const rotatorPosition = {
      x: bboxCenter.x,
      y: bbox.y - 50,
    };

    // Rotate for 180 degrees clockwise
    AtImageView.drawThroughPoints(
      [
        [rotatorPosition.x, rotatorPosition.y],
        [bboxCenter.x + 100, bboxCenter.y],
        [bboxCenter.x, bboxCenter.y + 100],
        [bboxCenter.x, bboxCenter.y + 200],
      ],
      "steps",
      10,
    );

    // When we have the  rotated region, we need to check its behavior when we drag it across the borders of the image
    let rectangleResult;

    I.say("Drag the region over the left border");
    AtImageView.drawThroughPoints(
      [
        [bboxCenter.x, bboxCenter.y],
        [-500, bboxCenter.y],
      ],
      "steps",
      20,
    );
    AtOutliner.seeSelectedRegion();
    // moving of the region should be constrained by borders
    rectangleResult = await LabelStudio.serialize();
    Asserts.deepEqualWithTolerance(
      (rectangleResult[0].value.x * canvasSize.width) / 100,
      Shape.byBBox(bbox.width, bbox.y + bbox.height, -bbox.width, -bbox.height).result.x,
    );
    // reset position by undo
    I.pressKey(["CommandOrControl", "z"]);

    I.say("Drag the region over the top border");
    AtImageView.drawThroughPoints(
      [
        [bboxCenter.x, bboxCenter.y],
        [bboxCenter.x, -500],
      ],
      "steps",
      20,
    );
    AtOutliner.seeSelectedRegion();
    // moving of the region should be constrained by borders
    rectangleResult = await LabelStudio.serialize();
    Asserts.deepEqualWithTolerance(
      (rectangleResult[0].value.y * canvasSize.height) / 100,
      Shape.byBBox(bbox.x + bbox.width, bbox.height, -bbox.width, -bbox.height).result.y,
    );
    // reset position by undo
    I.pressKey(["CommandOrControl", "z"]);

    I.say("Drag the region over the right border");
    AtImageView.drawThroughPoints(
      [
        [bboxCenter.x, bboxCenter.y],
        [canvasSize.width + 500, bboxCenter.y],
      ],
      "steps",
      20,
    );
    AtOutliner.seeSelectedRegion();
    // moving of the region should be constrained by borders
    rectangleResult = await LabelStudio.serialize();

    Asserts.deepEqualWithTolerance(
      (rectangleResult[0].value.x * canvasSize.width) / 100,
      Shape.byBBox(canvasSize.width, bbox.y + bbox.height, -bbox.width, -bbox.height).result.x,
    );
    // reset position by undo
    I.pressKey(["CommandOrControl", "z"]);

    I.say("Drag the region over the bottom border");
    AtImageView.drawThroughPoints(
      [
        [bboxCenter.x, bboxCenter.y],
        [bboxCenter.x, canvasSize.height + 500],
      ],
      "steps",
      20,
    );
    AtOutliner.seeSelectedRegion();
    // moving of the region should be constrained by borders
    rectangleResult = await LabelStudio.serialize();
    Asserts.deepEqualWithTolerance(
      (rectangleResult[0].value.y * canvasSize.height) / 100,
      Shape.byBBox(bbox.x + bbox.width, canvasSize.height, -bbox.width, -bbox.height).result.y,
    );
  },
);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasMultiSelectionRotator)).Scenario(
  "Limitation of dragging a couple of rotated regions",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();
    const canvasSize = await AtImageView.getCanvasSize();

    const bbox1 = {
      x: canvasSize.width / 2 - 50,
      y: canvasSize.height / 2 - 50,
      width: 50,
      height: 50,
    };

    const bbox2 = {
      x: canvasSize.width / 2,
      y: canvasSize.height / 2,
      width: 50,
      height: 50,
    };

    const transformerBbox = {
      x: bbox1.x,
      y: bbox1.y,
      width: bbox2.x + bbox2.width - bbox1.x,
      height: bbox2.y + bbox2.height - bbox1.y,
    };
    const transformerBboxCenter = {
      get x() {
        return transformerBbox.x + transformerBbox.width / 2;
      },
      get y() {
        return transformerBbox.y + transformerBbox.height / 2;
      },
    };

    // Draw the first region
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, bbox1.x, bbox1.y, bbox1.width, bbox1.height, AtImageView);
    AtOutliner.seeRegions(1);

    // Draw the second region
    I.pressKey(Shape.hotKey);
    I.pressKeyDown("CommandOrControl");
    drawShapeByBbox(Shape, bbox2.x, bbox2.y, bbox2.width, bbox2.height, AtImageView);
    I.pressKeyUp("CommandOrControl");
    AtOutliner.seeRegions(2);

    // Select them by move tool
    I.pressKey("v");
    AtImageView.drawThroughPoints(
      [
        [transformerBbox.x - 50, transformerBbox.y - 50],
        [transformerBbox.x + transformerBbox.width + 50, transformerBbox.y + transformerBbox.height + 50],
      ],
      "steps",
      10,
    );
    AtOutliner.seeSelectedRegion();

    // The rotator anchor must be above top anchor by 50 pixels
    const rotatorPosition = {
      x: transformerBboxCenter.x,
      y: transformerBbox.y - 50,
    };

    // Rotate for 180 degrees clockwise
    AtImageView.drawThroughPoints(
      [
        [rotatorPosition.x, rotatorPosition.y],
        [transformerBboxCenter.x + 100, transformerBboxCenter.y],
        [transformerBboxCenter.x, transformerBboxCenter.y + 100],
        [transformerBboxCenter.x, transformerBboxCenter.y + 200],
      ],
      "steps",
      10,
    );

    // When we have the  rotated region, we need to check its behavior when we drag it across the borders of the image
    let rectangleResult;

    I.say("Drag the region over the left border");
    AtImageView.drawThroughPoints(
      [
        [transformerBboxCenter.x, transformerBboxCenter.y],
        [-500, transformerBboxCenter.y],
      ],
      "steps",
      20,
    );
    AtOutliner.seeSelectedRegion();
    // moving of the region should be constrained by borders
    rectangleResult = await LabelStudio.serialize();
    Asserts.deepEqualWithTolerance(
      (rectangleResult[0].value.x * canvasSize.width) / 100,
      Shape.byBBox(transformerBbox.width, transformerBbox.y + transformerBbox.height, -bbox1.width, -bbox1.height)
        .result.x,
    );
    Asserts.deepEqualWithTolerance(
      (rectangleResult[1].value.x * canvasSize.width) / 100,
      Shape.byBBox(bbox2.width, transformerBbox.y + bbox2.height, -bbox2.width, -bbox2.height).result.x,
    );
    // reset position by undo
    I.pressKey(["CommandOrControl", "z"]);

    I.say("Drag the region over the top border");
    AtImageView.drawThroughPoints(
      [
        [transformerBboxCenter.x, transformerBboxCenter.y],
        [transformerBboxCenter.x, -500],
      ],
      "steps",
      20,
    );
    AtOutliner.seeSelectedRegion();
    // moving of the region should be constrained by borders
    rectangleResult = await LabelStudio.serialize();
    Asserts.deepEqualWithTolerance(
      (rectangleResult[0].value.y * canvasSize.height) / 100,
      Shape.byBBox(transformerBbox.x + transformerBbox.width, transformerBbox.height, -bbox1.width, -bbox1.height)
        .result.y,
    );
    Asserts.deepEqualWithTolerance(
      (rectangleResult[1].value.y * canvasSize.height) / 100,
      Shape.byBBox(transformerBbox.x + bbox2.width, bbox2.height, -bbox2.width, -bbox2.height).result.y,
    );
    // reset position by undo
    I.pressKey(["CommandOrControl", "z"]);

    I.say("Drag the region over the right border");
    AtImageView.drawThroughPoints(
      [
        [transformerBboxCenter.x, transformerBboxCenter.y],
        [canvasSize.width + 500, transformerBboxCenter.y],
      ],
      "steps",
      20,
    );
    AtOutliner.seeSelectedRegion();
    // moving of the region should be constrained by borders
    rectangleResult = await LabelStudio.serialize();

    Asserts.deepEqualWithTolerance(
      (rectangleResult[0].value.x * canvasSize.width) / 100,
      Shape.byBBox(canvasSize.width, transformerBbox.y + transformerBbox.height, -bbox1.width, -bbox1.height).result.x,
    );
    Asserts.deepEqualWithTolerance(
      (rectangleResult[1].value.x * canvasSize.width) / 100,
      Shape.byBBox(
        canvasSize.width - transformerBbox.width + bbox2.width,
        transformerBbox.y + bbox2.height,
        -bbox2.width,
        -bbox2.height,
      ).result.x,
    );
    // reset position by undo
    I.pressKey(["CommandOrControl", "z"]);

    I.say("Drag the region over the bottom border");
    AtImageView.drawThroughPoints(
      [
        [transformerBboxCenter.x, transformerBboxCenter.y],
        [transformerBboxCenter.x, canvasSize.height + 500],
      ],
      "steps",
      20,
    );
    AtOutliner.seeSelectedRegion();
    // moving of the region should be constrained by borders
    rectangleResult = await LabelStudio.serialize();
    Asserts.deepEqualWithTolerance(
      (rectangleResult[0].value.y * canvasSize.height) / 100,
      Shape.byBBox(transformerBbox.x + transformerBbox.width, canvasSize.height, -bbox1.width, -bbox1.height).result.y,
    );
    Asserts.deepEqualWithTolerance(
      (rectangleResult[1].value.y * canvasSize.height) / 100,
      Shape.byBBox(
        transformerBbox.x + bbox2.width,
        canvasSize.height - transformerBbox.height + bbox2.height,
        -bbox2.width,
        -bbox2.height,
      ).result.y,
    );
  },
);

Data(shapesTable.filter(({ shapeName }) => shapes[shapeName].hasRotator)).Scenario(
  "Rotating the region near the border",
  async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels, current }) => {
    const { shapeName } = current;
    const Shape = shapes[shapeName];
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");
    LabelStudio.init(getParamsWithShape(shapeName, Shape.params));
    AtDetailsPanel.collapsePanel();
    LabelStudio.waitForObjectsReady();
    AtOutliner.seeRegions(0);
    await AtImageView.lookForStage();
    const canvasSize = await AtImageView.getCanvasSize();

    const bbox = {
      x: canvasSize.width - Math.ceil(Math.sqrt(100 ** 2 + 100 ** 2)) / 2 - 50,
      y: canvasSize.height - Math.ceil(Math.sqrt(100 ** 2 + 100 ** 2)) / 2 - 50,
      width: 100,
      height: 100,
    };

    const bboxCenter = {
      x: bbox.x + bbox.width / 2,
      y: bbox.y + bbox.height / 2,
    };

    // Draw the region
    I.pressKey(Shape.hotKey);
    drawShapeByBbox(Shape, bbox.x, bbox.y, bbox.width, bbox.height, AtImageView);
    AtOutliner.seeRegions(1);

    // Select it
    AtImageView.clickAt(bboxCenter.x, bboxCenter.y);
    AtOutliner.seeSelectedRegion();

    // The rotator anchor must be above top anchor by 50 pixels
    const rotatorPosition = {
      x: bboxCenter.x,
      y: bbox.y - 50,
    };

    // Check 7 different rotations
    const rotatorWayPoints = [[rotatorPosition.x, rotatorPosition.y]];
    const angle45 = Math.PI / 4;

    for (let i = 0; i < 8; i++) {
      const angle = angle45 * i;

      rotatorWayPoints.push([bboxCenter.x + Math.sin(angle) * 100, bboxCenter.y - Math.cos(angle) * 100]);
      rotatorWayPoints.push([bboxCenter.x + Math.sin(angle) * 1000, bboxCenter.y - Math.cos(angle) * 1000]);

      // Rotate clockwise by 45 * i degrees
      AtImageView.drawThroughPoints(rotatorWayPoints, "steps", 10);
      AtOutliner.seeSelectedRegion();
      // Check that rotating was successful
      const rectangleResult = await LabelStudio.serialize();

      Asserts.deepEqualWithTolerance(Math.round(rectangleResult[0].value.rotation), 45 * i);

      // undo rotation
      I.pressKey(["CommandOrControl", "z"]);
      // clear unnecessary waypoints
      rotatorWayPoints.pop();
    }
  },
);
